﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using HeapSpecSupport;
using SimpleIR;

namespace Evaluator.EvalState
{
	/// <summary>
	/// A map from field names to the values that they contain implemented as a library that works on sorted arrays 
	/// of <c>BAPValueEntrys</c>. We do it this way to avoid creating a class that is just a wrapper of an array.
	/// </summary>
	internal sealed class BAPLayoutMap
	{
		private BAPLayoutEntry[] m_entries;
		private UseModRecord[] m_umdata;

		private BAPLayoutMap(BAPLayoutEntry[] entries, UseModRecord[] umr)
		{
			this.m_entries = entries;
			this.m_umdata = umr;
		}

		/// <summary>
		/// Given a set of fields create the array map for them with all the values set to the defaults.
		/// </summary>
		internal BAPLayoutMap(HashSet<IRBasicAccessPath> allbap, ulong time, long line)
		{
			Contract.Requires(allbap != null);

			this.m_entries = new BAPLayoutEntry[allbap.Count];
			this.m_umdata = new UseModRecord[allbap.Count];

			int i = 0;

			foreach(IRBasicAccessPath bap in allbap)
			{
				if(bap.PathLast.FieldType is IRPtrType)
					this.m_entries[i] = BAPLayoutEntry.MakeInitialField(BAPLayoutEntry.INVALID_ARRAY_INDEX, bap, EPointer.CreateDefaultNullPointer((IRPtrType)bap.PathLast.FieldType));
				else if(bap.PathLast.FieldType is IREnumType)
					this.m_entries[i] = BAPLayoutEntry.MakeInitialField(BAPLayoutEntry.INVALID_ARRAY_INDEX, bap, ENumeric.CreateDefaultValueForType(((IREnumType)bap.PathLast.FieldType).UnerlyingNumericType));
				else
					this.m_entries[i] = BAPLayoutEntry.MakeInitialField(BAPLayoutEntry.INVALID_ARRAY_INDEX, bap, ENumeric.CreateDefaultValueForType(((IRNumericType)bap.PathLast.FieldType)));

				this.m_umdata[i] = UseModRecord.MakeInitialUseMod(time, line);
				++i;
			}

			Array.Sort(this.m_entries);
		}

		/// <summary>
		/// Given a set of fields create the array map for them with all the values set to the defaults.
		/// </summary>
		internal BAPLayoutMap(int arraysize, HashSet<IRBasicAccessPath> cntbap, HashSet<IRBasicAccessPath> oap, ulong time, long line)
		{
			Contract.Requires(arraysize >= 0);
			Contract.Requires(cntbap != null);
			Contract.Requires(oap != null);

			this.m_entries = new BAPLayoutEntry[(cntbap.Count * arraysize) + oap.Count];
			this.m_umdata = new UseModRecord[(cntbap.Count * arraysize) + oap.Count];
			int i = 0;

			foreach(IRBasicAccessPath bap in cntbap)
			{
				for(int j = 0; j < arraysize; ++j)
				{
					if(bap.PathLast.FieldType is IRPtrType)
						this.m_entries[i] = BAPLayoutEntry.MakeInitialField(j, bap, EPointer.CreateDefaultNullPointer((IRPtrType)bap.PathLast.FieldType));
					else if(bap.PathLast.FieldType is IREnumType)
						this.m_entries[i] = BAPLayoutEntry.MakeInitialField(j, bap, ENumeric.CreateDefaultValueForType(((IREnumType)bap.PathLast.FieldType).UnerlyingNumericType));
					else
						this.m_entries[i] = BAPLayoutEntry.MakeInitialField(j, bap, ENumeric.CreateDefaultValueForType(((IRNumericType)bap.PathLast.FieldType)));

					this.m_umdata[i] = UseModRecord.MakeInitialUseMod(0, -1);
					++i;
				}
			}

			foreach(IRBasicAccessPath bap in cntbap)
			{
				if(bap.PathLast.FieldType is IRPtrType)
					this.m_entries[i] = BAPLayoutEntry.MakeInitialField(BAPLayoutEntry.INVALID_ARRAY_INDEX, bap, EPointer.CreateDefaultNullPointer((IRPtrType)bap.PathLast.FieldType));
				else if(bap.PathLast.FieldType is IREnumType)
					this.m_entries[i] = BAPLayoutEntry.MakeInitialField(BAPLayoutEntry.INVALID_ARRAY_INDEX, bap, ENumeric.CreateDefaultValueForType(((IREnumType)bap.PathLast.FieldType).UnerlyingNumericType));
				else
					this.m_entries[i] = BAPLayoutEntry.MakeInitialField(BAPLayoutEntry.INVALID_ARRAY_INDEX, bap, ENumeric.CreateDefaultValueForType(((IRNumericType)bap.PathLast.FieldType)));

				this.m_umdata[i] = UseModRecord.MakeInitialUseMod(time, line);
				++i;
			}

			Array.Sort(this.m_entries);
		}

		/// <summary>
		/// Create a deep copy of the given <c>BAPLayoutMap</c>.
		/// </summary>
		internal BAPLayoutMap CopyBapLayout()
		{
			BAPLayoutEntry[] entries = (BAPLayoutEntry[])this.m_entries.Clone();
			UseModRecord[] umr = (UseModRecord[])this.m_umdata.Clone();
			return new BAPLayoutMap(entries, umr);
		}

		public override string ToString()
		{
			StringBuilder fmap = new StringBuilder("(");
			for(int i = 0; i < this.m_entries.Length; ++i)
			{
				if(i != 0)
					fmap.Append(", ");
				fmap.Append(this.m_entries[i].ToString());
				fmap.Append(": " + this.m_umdata[i].ToString());
			}
			fmap.Append(")");

			return fmap.ToString();
		}

		/// <summary>
		/// Read a value from a field.
		/// </summary>
		internal EMemoryValue ReadFieldValue(int arraypos, IRBasicAccessPath loc, ulong time, long line)
		{
			Contract.Requires((arraypos == BAPLayoutEntry.INVALID_ARRAY_INDEX) | (arraypos >= 0));
			Contract.Requires(loc != null && (!loc.IsEmptyAccessPath));
			
			BAPLayoutEntry key = new BAPLayoutEntry(arraypos, loc, null);
			int pos = Array.BinarySearch(this.m_entries, key);

			this.m_umdata[pos].DoRead(time, line);
			return this.m_entries[pos].Value;
		}

		/// <summary>
		/// Read values from all prefix fields.
		/// </summary>
		internal void ReadFieldValues(int arraypos, IRBasicAccessPath loc, BAPLayoutMap into, ulong time, long line)
		{
			Contract.Requires(loc != null);
			Contract.Requires(into != null);

			int startpos = 0;
			int endpos = this.m_entries.Length;
			
			if(arraypos != BAPLayoutEntry.INVALID_ARRAY_INDEX)
			{
				BAPLayoutEntry keylow = new BAPLayoutEntry(arraypos, loc, null);
				BAPLayoutEntry keyhigh = new BAPLayoutEntry(arraypos + 1, loc, null);

				startpos = Array.BinarySearch(this.m_entries, keylow);
				endpos = Array.BinarySearch(this.m_entries, keyhigh);
			}
			
			for(int i = startpos; i < endpos; ++i)
			{
				if(loc.IsAccessPathPrefixOf(this.m_entries[i].BAPPath))
				{
					IRBasicAccessPath nbap = (loc.IsEmptyAccessPath ? this.m_entries[i].BAPPath : ExeProgram.TProg.GetAccessPathTailK(this.m_entries[i].BAPPath, loc.Path.Length));
					this.m_umdata[i].DoRead(time, line);
					into.WriteFieldValue(BAPLayoutEntry.INVALID_ARRAY_INDEX, nbap, this.m_entries[i].Value, time, line);
				}
			}
		}

		/// <summary>
		/// Write a value to a field.
		/// </summary>
		internal void WriteFieldValue(int arraypos, IRBasicAccessPath loc, EMemoryValue val, ulong time, long line)
		{
			Contract.Requires((arraypos == BAPLayoutEntry.INVALID_ARRAY_INDEX) | (arraypos >= 0));
			Contract.Requires(loc != null && (!loc.IsEmptyAccessPath));
			Contract.Requires(((loc.PathLast.FieldType is IREnumType) && ((IREnumType)loc.PathLast.FieldType).UnerlyingNumericType == val.TypeOfValue) | (loc.PathLast.FieldType == val.TypeOfValue), "Types do not match -- must be correctly cast to exact match on store.");
			
			BAPLayoutEntry key = new BAPLayoutEntry(arraypos, loc, null);
			int pos = Array.BinarySearch(this.m_entries, key);

			this.m_umdata[pos].DoWrite(time, line);
			this.m_entries[pos].Value = val;
		}

		/// <summary>
		/// Write values to all prefix fields.
		/// </summary>
		internal void WriteFieldValues(int arraypos, BAPLayoutMap from, IRBasicAccessPath loc, ulong time, long line)
		{
			Contract.Requires(from != null);
			Contract.Requires(loc != null);
			
			for(int i = 0; i < from.m_entries.Length; ++i)
			{
				IRBasicAccessPath wpath = (loc.IsEmptyAccessPath ? from.m_entries[i].BAPPath : ExeProgram.TProg.GetAccessPathExtensionPost_WPath(loc, from.m_entries[i].BAPPath));
				BAPLayoutEntry key = new BAPLayoutEntry(arraypos, wpath, null);
				int pos = Array.BinarySearch(this.m_entries, key);

				this.m_umdata[pos].DoWrite(time, line);
				this.m_entries[pos].Value = from.m_entries[i].Value;
			}
		}
	}

	/// <summary>
	/// Simple struct for organizing field to value mapping (along with use/mod info).
	/// </summary>
	internal struct BAPLayoutEntry : IEquatable<BAPLayoutEntry>, IComparable<BAPLayoutEntry>
	{
		internal const int INVALID_ARRAY_INDEX = -1;

		internal readonly int ArrayPos;
		internal readonly IRBasicAccessPath BAPPath;
		
		//is one of the two values, need to look at the type of the location to know which one.
		internal EMemoryValue Value;
		
		internal BAPLayoutEntry(int arraypos, IRBasicAccessPath bap, EMemoryValue val)
		{
			Contract.Requires(bap != null && (!bap.IsEmptyAccessPath));
			
			this.ArrayPos = arraypos;
			this.BAPPath = bap;
			this.Value = val;
		}

		internal static BAPLayoutEntry MakeInitialField(int arraypos, IRBasicAccessPath bap, EMemoryValue val)
		{ return new BAPLayoutEntry(arraypos, bap, val); }

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public static bool operator ==(BAPLayoutEntry fve1, BAPLayoutEntry fve2)
		{ return (fve1.ArrayPos == fve2.ArrayPos) & (fve1.BAPPath == fve2.BAPPath); }

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public static bool operator !=(BAPLayoutEntry fve1, BAPLayoutEntry fve2)
		{ return (fve1.ArrayPos != fve2.ArrayPos) | (fve1.BAPPath != fve2.BAPPath); }

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public static bool operator <(BAPLayoutEntry fve1, BAPLayoutEntry fve2)
		{
			if(fve1.ArrayPos != fve2.ArrayPos)
				return fve1.ArrayPos < fve2.ArrayPos;

			return fve1.BAPPath.CompareTo(fve2.BAPPath) < 0; 
		}

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public static bool operator >(BAPLayoutEntry fve1, BAPLayoutEntry fve2)
		{
			if(fve1.ArrayPos != fve2.ArrayPos)
				return fve1.ArrayPos > fve2.ArrayPos;

			return fve1.BAPPath.CompareTo(fve2.BAPPath) > 0;
		}

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public override int GetHashCode()
		{ return this.ArrayPos ^ this.BAPPath.GetHashCode(); }

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public override bool Equals(Object obj)
		{ return (obj is BAPLayoutEntry) && ((this.ArrayPos == ((BAPLayoutEntry)obj).ArrayPos) & (this.BAPPath == ((BAPLayoutEntry)obj).BAPPath)); }

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public bool Equals(BAPLayoutEntry other)
		{ return (this.ArrayPos == other.ArrayPos) & (this.BAPPath == other.BAPPath); }

		/// <summary>
		/// ONLY ON ACCESS PATH!!
		/// </summary>
		public int CompareTo(BAPLayoutEntry other)
		{
			if(this.ArrayPos != other.ArrayPos)
				return this.ArrayPos - other.ArrayPos;

			return this.BAPPath.CompareTo(other.BAPPath); 
		}

		public override string ToString()
		{ return String.Format("{0} -> {1}", this.BAPPath.ToString(), this.Value.ToString()); }
	}

	/// <summary>
	/// Simple struct for tracking use/mod info for fixed atomic (primitive, pointer, reference) locations.
	/// </summary>
	internal struct UseModRecord
	{
		internal ulong UseTime;
		internal long UseLine;
		internal ulong ModTime;
		internal long ModLine;

		internal UseModRecord(ulong utime, long uline, ulong mtime, long mline)
		{
			this.UseTime = utime;
			this.UseLine = uline;
			this.ModTime = mtime;
			this.ModLine = mline;
		}

		internal static UseModRecord MakeInitialUseMod(ulong time, long line)
		{ return new UseModRecord(time, line, time, line); }

		/// <summary>
		/// Simulate a read from this value. Update the use time/line and return the value.
		/// </summary>
		internal void DoRead(ulong time, long line)
		{
			if((this.ModTime == 0) & (this.ModLine == -1))
			{
				DebugReport.ReportError("Read from uninitialized container/array. We assume must be explicitly initialized!!!!");
				throw new InvalidOperationException("Read from uninitialized location!!!");
			}

			this.UseTime = time;
			this.UseLine = line;
		}

		/// <summary>
		/// Simulate a write to this value. Update the use time/line and update the value.
		/// </summary>
		internal void DoWrite(ulong time, long line)
		{
			this.ModTime = time;
			this.ModLine = line;
		}

		public override string ToString()
		{ return String.Format("Use({0:d}T {1:d}L) Mod({2:d}T {3:d}L)", this.UseTime, this.UseLine, this.ModTime, this.ModLine); }
	}
}
